/*
 * Copyright 2010 Adrian Buerki
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.gwt.gwtai.applet.generator;

import java.io.PrintWriter;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.gwtai.applet.client.Align;
import com.google.gwt.gwtai.applet.client.AppletClassName;
import com.google.gwt.gwtai.applet.client.Archive;
import com.google.gwt.gwtai.applet.client.Codebase;
import com.google.gwt.gwtai.applet.client.Height;
import com.google.gwt.gwtai.applet.client.ImplementingClass;
import com.google.gwt.gwtai.applet.client.JavaArguments;
import com.google.gwt.gwtai.applet.client.JavaVersion;
import com.google.gwt.gwtai.applet.client.LoadingImage;
import com.google.gwt.gwtai.applet.client.Params;
import com.google.gwt.gwtai.applet.client.SeparateJVM;
import com.google.gwt.gwtai.applet.client.Width;

/**
 * GWT Generator that creates the proxy classes to access the applet. The proxy
 * classes consist mostly of native JavaScript code embedded with GWT's JNSI
 * functionality. 
 * 
 * @author Adrian Buerki <a.buerki@gmail.com>
 */
public class AppletProxyGenerator extends Generator {
	// private static HashMap<String, String> _createdClassNames = new HashMap<String, String>();;

	public String generate(TreeLogger logger, GeneratorContext context,
			String typeName) throws UnableToCompleteException {


		// String createdClassName = _createdClassNames.get(typeName);

		// if (null == createdClassName) {
			JClassType classType;

			try {
				classType = context.getTypeOracle().getType(typeName);
			} catch (Exception e) {
				logger.log(Type.ERROR, e.getMessage(), e);

				throw new UnableToCompleteException();
			}

			String packageName = classType.getPackage().getName();
			String simpleName = classType.getSimpleSourceName() + "Impl";
			
			PrintWriter printWriter = context.tryCreate(logger, packageName,
					simpleName);
			
			if (printWriter == null) {
				// Has already been created
				return packageName +"." + simpleName;
			}

			ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
					packageName, simpleName);

			composer.addImport("java.util.HashMap");
			composer
					.addImport("com.google.gwt.gwtai.applet.client.AppletCallback");
			composer.addImport("com.google.gwt.dom.client.Element");
			composer.addImport("com.google.gwt.user.client.DOM");

			composer.addImplementedInterface(typeName);
			composer
					.setSuperclass("com.google.gwt.gwtai.applet.client.AppletAccomplice");
			
			SourceWriter sw = composer.createSourceWriter(context, printWriter);

			JMethod[] methods = classType.getOverridableMethods();

			for (JMethod method : methods) {
				String methodDeclaration = method.getReadableDeclaration(false,
						false, false, false, true);
				methodDeclaration = methodDeclaration.replace("public",
						"public native");

				sw.println();
				sw.indent();
				sw.print(methodDeclaration);
				sw.println(" /*-{");
				sw.indent();
				sw.println("var id = this.@" +packageName +"." + simpleName + "::getName()();");
				sw.println("var gwtElem = @com.google.gwt.user.client.DOM::getElementById(Ljava/lang/String;)(id);");

				JParameter[] methodParams = method.getParameters();
				String paramList = "";

				int i = 0;

				for (JParameter param : methodParams) {
					paramList += param.getName();

					if ((i + 1) < method.getParameters().length) {
						paramList += ",";
						i++;
					}
				}

				JType type = method.getReturnType();

				if (!type.getSimpleSourceName().equals("void")) {
					sw.println("return gwtElem['" + method.getName() + "']("
							+ paramList + ");");
				} else {
					sw.println("gwtElem['" + method.getName() + "']("
							+ paramList + ");");
				}

				sw.outdent();
				sw.println("}-*/;");
				sw.outdent();
			}

			sw.println();
			sw.indent();
			sw.print("protected native boolean isAppletActive()");
			sw.println(" /*-{");
			sw.indent();
			sw.print("var id = '");
			sw.println(simpleName + "';");
			sw
					.println("var gwtElem = @com.google.gwt.user.client.DOM::getElementById(Ljava/lang/String;)(id);");
			sw.println("return gwtElem['isActive']();");
			sw.outdent();
			sw.println("}-*/;");
			sw.outdent();

			ImplementingClass implementingClass = classType
					.getAnnotation(ImplementingClass.class);

			String appletName;

			if (null == implementingClass) {
				AppletClassName appletClassName = classType
						.getAnnotation(AppletClassName.class);
				
				if (null == appletClassName) {
					logger.log(Type.ERROR, "No Applet class name given, use the ImplementingClass or AppletClassName annotation.");

					throw new UnableToCompleteException();
				} else {
					appletName = appletClassName.value();
				}
			} else {
				appletName = implementingClass.value().getName();
			}

			sw.println();
			sw.indent();
			sw.println("public String getCode() {");
			sw.indent();
			sw.print("return \"");
			sw.print(appletName + ".class");
			sw.println("\";");
			sw.outdent();
			sw.println("}");
			sw.outdent();

			Width widthAttribute = classType.getAnnotation(Width.class);

			sw.println();
			sw.indent();
			sw.println("public String getWidth() {");
			sw.indent();
			sw.print("return ");

			if (null == widthAttribute) {
				// Default to 350
				sw.print("\"350\"");
			} else {
				sw.print("\"" + widthAttribute.value() + "\"");
			}

			sw.println(";");
			sw.outdent();
			sw.println("}");
			sw.outdent();

			Height heigthAttribute = classType.getAnnotation(Height.class);

			sw.println();
			sw.indent();
			sw.println("public String getHeight() {");
			sw.indent();
			sw.print("return ");

			if (null == heigthAttribute) {
				// Default to 350
				sw.print("\"350\"");
			} else {
				sw.print("\"" + heigthAttribute.value() + "\"");
			}

			sw.println(";");
			sw.outdent();
			sw.println("}");
			sw.outdent();

			Align alignAttribute = classType.getAnnotation(Align.class);

			if (null != alignAttribute) {
				sw.println();
				sw.indent();
				sw.println("public AppletCallback getAlign() {");
				sw.indent();
				sw.print("return \"");
				sw.print(alignAttribute.value().name());
				sw.println("\";");
				sw.outdent();
				sw.println();
				sw.println("}");
				sw.outdent();
			}

			Archive archiveAttribute = classType.getAnnotation(Archive.class);

			if (null != archiveAttribute) {
				sw.println();
				sw.indent();
				sw.println("public String getArchive() {");
				sw.indent();
				sw.print("return \"");
				sw.print(archiveAttribute.value());
				sw.println("\";");
				sw.outdent();
				sw.println("}");
				sw.outdent();
			}
			
			JavaVersion javaVersionAttribute = classType.getAnnotation(JavaVersion.class);

			if (null != javaVersionAttribute) {
				sw.println();
				sw.indent();
				sw.println("public String getJavaVersion() {");
				sw.indent();
				sw.print("return \"");
				sw.print(javaVersionAttribute.value());
				sw.println("\";");
				sw.outdent();
				sw.println("}");
				sw.outdent();
			}
			
			JavaArguments javaArgumentsAttribute = classType.getAnnotation(JavaArguments.class);

			if (null != javaArgumentsAttribute) {
				sw.println();
				sw.indent();
				sw.println("public String getJavaArguments() {");
				sw.indent();
				sw.print("return \"");
				sw.print(javaArgumentsAttribute.value());
				sw.println("\";");
				sw.outdent();
				sw.println("}");
				sw.outdent();
			}
			
			SeparateJVM separateJVM = classType.getAnnotation(SeparateJVM.class);

			if (null != separateJVM) {
				sw.println();
				sw.indent();
				sw.println("public Boolean hasSeparateJVM() {");
				sw.indent();
				sw.print("return \"");
				sw.print(javaArgumentsAttribute.value());
				sw.println("\";");
				sw.outdent();
				sw.println("}");
				sw.outdent();
			}
			
			LoadingImage loadingImage = classType.getAnnotation(LoadingImage.class);

			if (null != loadingImage) {
				sw.println();
				sw.indent();
				sw.println("public String getLoadingImage() {");
				sw.indent();
				sw.print("return \"");
				sw.print(loadingImage.value());
				sw.println("\";");
				sw.outdent();
				sw.println("}");
				sw.outdent();
			}

			Codebase codeBaseAttribute = classType.getAnnotation(Codebase.class);

			if (null != codeBaseAttribute) {
				sw.println();
				sw.indent();
				sw.println("public String getCodebase() {");
				sw.indent();
				sw.print("return \"");
				sw.print(codeBaseAttribute.value());
				sw.println("\";");
				sw.outdent();
				sw.println();
				sw.println("}");
				sw.outdent();
			}

			Params paramAttribute = classType.getAnnotation(Params.class);

			if (null != paramAttribute) {
				sw.println();
				sw.indent();
				sw.println("public HashMap<String, String> getParameters() {");
				sw.indent();
				sw
						.println("HashMap<String, String> props = new HashMap<String, String>();");

				String[] names = paramAttribute.names();
				String[] values = paramAttribute.values();

				for (int i = 0; i < names.length; i++) {
					sw.print("props.put(\"");
					sw.print(names[i]);
					sw.print("\", \"");
					sw.print(values[i]);
					sw.println("\");");
				}

				sw.print("return props;");
				sw.outdent();
				sw.println();
				sw.println("}");
				sw.outdent();
			}

			sw.commit(logger);

			String createdClassName = composer.getCreatedClassName();

			// _createdClassNames.put(typeName, createdClassName);
		// }

		return createdClassName;
	}

}
